Repository navigation
fix(server)!: reject a wildcard bind with no advertised address - #3923
Conversation
|
Thanks for the PR. It is labeled Slash commands (own line, regular comment) move it around the queue:
See CONTRIBUTING.md for details. |
Closes apache#3890 A server whose TCP listener binds a wildcard reported that wildcard as its own client-facing address in GetClusterMetadata. SDKs collect roster addresses into their reconnect candidates, so 0.0.0.0:8090 became a dial target that retry would eventually pick and never reach. A bind address answers which interfaces a node accepts on. It is not an answer to where a client reaches it, and for the unspecified address the two have no relation. With a roster each node already answers the second question through cluster.nodes.advertised_address; without one the server had no way to be told it at all. node.advertised_address supplies it, named to match its roster counterpart, and the server now refuses to start when a wildcard bind leaves the question unanswered. A concrete bind address still needs no declaration: it already names an interface a client can reach. Only what an operator declares is validated. A bind address is never held to being routable, which is the mistake that made Kafka reject wildcard binds that had always been valid (KAFKA-18281). The declared values are held to it in both modes, so a roster ip, advertised address or per-network selector that names the unspecified address stops the boot rather than the cluster: peers dialing 0.0.0.0 reach their own host, which is how a cluster comes up with every node believing it is alone while its containers report healthy. Resolution is now infallible past construction. A roster node is built through TryFrom, so an address that does not parse fails there instead of leaving every consumer to carry a fallback, and the fallbacks are gone: metadata no longer publishes a raw unparsed string, forwarding no longer treats a missing replica ip as "no target", and a selector whose CIDR does not parse is no longer dropped in silence. The two listeners also resolve the self address once between them, rather than each deriving it from its own bind address and disagreeing whenever http.address and tcp.address differ. BREAKING CHANGE: a server that binds a wildcard address without a roster now refuses to start until node.advertised_address names where clients reach it. Deployments that bind 0.0.0.0 must declare one: the Helm chart derives it from the Service DNS name and the shipped compose files name their service, but an external deployment needs the hostname or load balancer address its clients dial. A cluster.nodes ip must now be a literal IP, and no declared address may be the unspecified one.
60f1345 to
2b6fc1a
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3923 +/- ##
============================================
- Coverage 84.95% 84.94% -0.02%
Complexity 1405 1405
============================================
Files 1224 1225 +1
Lines 179327 179727 +400
Branches 145615 146018 +403
============================================
+ Hits 152347 152668 +321
- Misses 22958 23009 +51
- Partials 4022 4050 +28
🚀 New features to boost your workflow:
|
mmodzelewski
left a comment
There was a problem hiding this comment.
The core logic is sound, there are a few things worth fixing before merge, though. Besides the comments in-line, here are the ones that don't have a direct reference in the changed code:
- docker-compose.yml (root) — builds the Dockerfile (wildcard binds at Dockerfile:45-48), has no environment: block, and cluster is off by default, so the README-documented docker compose up quickstart now refuses boot. Needs IGGY_NODE_ADVERTISED_ADDRESS=localhost like the eight other compose files patched in this PR.
- foreign/java/java-sdk/src/test/java/org/apache/iggy/client/BaseIntegrationTest.java:92 — the testcontainer sets IGGY_TCP_ADDRESS=0.0.0.0:8090 with no advertised var; it will refuse boot once apache/iggy:edge ships this change. The C# fixture got the fix (VsrCluster.cs:294); the Java one was missed. Dormant today only because CI takes the USE_EXTERNAL_SERVER path.
- core/server/src/args.rs:57 — the --help example IGGY_TCP_ADDRESS=0.0.0.0:8090 is now boot-refusing when copy-pasted standalone; the READMEs were updated but the help text was missed.
e581b2f to
850c340
Compare
|
/ready |
There was a problem hiding this comment.
two things outside the diff:
.devcontainer/devcontainer.jsonstill binds every listener to0.0.0.0with noIGGY_NODE_ADVERTISED_ADDRESS, socargo run --bin iggy-serverinside the devcontainer refuses to start. add"IGGY_NODE_ADVERTISED_ADDRESS": "localhost"there (ports are forwarded to localhost).- keeping the refusal for the bare image is the right call (a baked
localhostwould give go clients in other containers a self-dial reconnect candidate). the website quick start only passes-e IGGY_TCP_ADDRESS=0.0.0.0:8090though, so it needs the extra-ein lock-step
optional, non-blocking:
ClusterConfig::validatenow repeats every per-node check thatTryFrom<ClusterNodeConfig>already does, with near-identical messages that have started to drift (1056 has the hint, 499 doesn't). callingResolvedClusterNode::try_from(node.clone())?in the loop and building the conflict pool from the resolved node drops about 70 lines, and removes thenode.ip.parse::<AdvertisedAddress>().ok()at 1150 that can't beNoneanymore.- parse-then-
is_unspecified()appears 5 times; rejecting the wildcard insideFromStr for AdvertisedAddress(afterto_canonical()) removes the method and all five branches, and stops::ffff:10.0.0.1and10.0.0.1being treated as two different addresses. - helm README says the server "refuses to start without it", but the chart's default image tag
0.7.0predates the setting and just logs an unknown-variable warning. [node]in config.toml sits between[http.tls]and[tcp], reads like part of the http block.- cluster_meta.rs:50 says a bracketed
[2001:db8::1]gets bracketed twice by clients - true for the go and C# sdks, not the rust one, which just doesformat!("{}:{}").
|
/ready |
I'll update the iggy-website btw. |
Merging master pulled in the partition forward path (apache#3987), written against the pre-branch roster API: replica_ip() as an Option, ClusterRoster.self_ip, and an infallible From for ResolvedClusterNode. This branch validates roster IPs at config time, so replica_ip() is plain and the conversion is a TryFrom. Git merged the hunks without a textual conflict and left the server crate uncompilable. Adapt the forward path and its test helper to the resolved roster. The unparsable-IP case in the roster walk test is gone: such a node can no longer exist in a resolved roster.
c5aefe3
The Pinot suite could not start the current apache/iggy:edge image: the server now refuses a wildcard bind without an advertised address (#3923) and the suite never set one. It also re-pulled the image on every run, which made it far slower than the SDK suite, and its readiness probe accepted any HTTP status below 500 from /. The SDK suite in turn still claimed the published image shipped the legacy server and only waited for the ports to open. Both suites now configure the container the same way: advertise an address clients can dial, let the server use every core, and gate on /ping before running tests. The Pinot suite reuses the locally cached image like the SDK suite does.
) The Pinot suite could not start the current apache/iggy:edge image: the server now refuses a wildcard bind without an advertised address (#3923) and the suite never set one. It also re-pulled the image on every run, which made it far slower than the SDK suite, and its readiness probe accepted any HTTP status below 500 from /. The SDK suite in turn still claimed the published image shipped the legacy server and only waited for the ports to open. Both suites now configure the container the same way: advertise an address clients can dial, let the server use every core, and gate on /ping before running tests. The Pinot suite reuses the locally cached image like the SDK suite does.
Which issue does this PR address?
Closes #3890
This PR block #3650, see #3890 for detail.
Rationale
A wildcard bind says which interfaces a node accepts on, not where a client reaches it, so publishing it as an address hands clients a target they cannot dial.
What changed?
node.advertised_addresssupplies it, and the server now refuses to start when server is configured with wildcard bind while leaves it unset.Breaking:
deployments binding
0.0.0.0without a roster must declare an address. The Helm chart can derives it from the Service DNS name and the shipped compose files name their service; anything else needsIGGY_NODE_ADVERTISED_ADDRESS. Acluster.nodesip must now be a literal IP, and no declared address may be the unspecified one.Some detail regarding new behavior:
When would it boot?
Standalone (
cluster.enabled = false)tcp.addressnode.advertised_address127.0.0.1:8090127.0.0.1(derived from the bind)127.0.0.1:8090broker.example.combroker.example.com(declared wins)0.0.0.0:80900.0.0.0:8090broker.example.combroker.example.com0.0.0.0/::broker:8090(carries a port)Cluster (
cluster.enabled = true)tcp.addressonly picks the bind interface here — ports come from the roster — so a wildcard is perfectlynormal.
nodes[].ip172.28.0.101nodes[].ip0.0.0.0/::nodes[].ipiggy-server(any hostname)advertised_addressadvertised_addressipadvertised_addressbroker.example.comadvertised_address0.0.0.0/::address0.0.0.0/::node.advertised_addressWhich address a client is told (
advertised_for):Both modes
tcp.address:8090(empty host)SocketAddrhas no such spellinglocalhost:8090(hostname)Edge cases
cluster.enabled = falsewith[[cluster.nodes]]left behindThat last row is a side fix: a malformed selector used to be swallowed, surfacing only as "clients on one network
get the catch-all address" with nothing to debug.
Local Execution
AI Usage